package physics.component;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import physics.Force;
import physics.Simulation;


/**
 * Spring object that applies forces based on Hooke's law on its connecing
 * masses.
 *
 * @author Robert C. Duvall
 */
public class Spring extends PhysicalObject {

    /**
     * The threshold length that defines a transitionary state where between
     * negative and positive lengths.
     */
    private static final double TRANSITION_LENGTH = 0.001;
    /**
     * The default k-value to use for a spring.
     */
    private static final double DEFAULT_SPRING_CONSTANT = 0.05;

    private final Mass myStart;
    private final Mass myEnd;
    private double myLength;
    private final double myK;

    private int myStartX;
    private int myStartY;
    private int myEndX;
    private int myEndY;

    private double myDX;
    private double myDY;

    /**
     * @param start Mass at first end of the spring
     * @param end Mass at second end of the spring
     * @param length the length of the spring at rest
     * @param kVal spring constant
     */
    public Spring (final Mass start, final Mass end, final double length,
            final double kVal) {
        myStart = start;
        myEnd = end;
        myLength = length;
        myK = kVal;
    }

    /**
     * Spring constructor that assumes a default k-value.
     *
     * @param start Mass at first end of the spring
     * @param end Mass at second end of the spring
     * @param length the length of the spring at rest
     */
    public Spring (final Mass start, final Mass end, final double length) {
        this(start, end, length, DEFAULT_SPRING_CONSTANT);
    }

    /**
     * Spring constructor that assumes a default k-value and uses the distance
     * between objects as the rest length.
     *
     * @param start Mass at first end of the spring
     * @param end Mass at second end of the spring
     */
    public Spring (final Mass start, final Mass end) {
        this(start, end, start.getDistanceBetween(end));
    }

    @Override
    public void drawShape (final Graphics2D pen) {
        determineCurrentPosition();
        pen.drawLine(myStartX, myStartY, myEndX, myEndY);
    }

    @Override
    protected void determineColor (final Graphics2D pen) {
        double len = Math.sqrt(myDX * myDX + myDY * myDY) - myLength;
        if (Math.abs(len) < TRANSITION_LENGTH) {
            pen.setColor(Color.WHITE);
        }
        else if (len < 0.0) {
            pen.setColor(Color.BLUE);
        }
        else {
            pen.setColor(Color.RED);
        }
    }

    /**
     * Updates variables pertaining to each end of the spring.
     */
    public final void determineCurrentPosition () {
        myStartX = (int) myStart.getCenter().getX();
        myStartY = (int) myStart.getCenter().getY();
        myEndX = (int) myEnd.getCenter().getX();
        myEndY = (int) myEnd.getCenter().getY();
    }

    /**
     * Updates the spring's horizontal and vertical length.
     */
    public final void calculateDxDy () {
        Point2D start = myStart.getCenter();
        Point2D end = myEnd.getCenter();
        myDX = start.getX() - end.getX();
        myDY = start.getY() - end.getY();
    }

    /**
     * Apply equal and opposite spring forces to each mass attached to spring.
     *
     * @param canvas area of screen upon which simulation takes place
     * @param dt time that has passed since last update call
     */
    @Override
    public void update (final Simulation canvas, final double dt) {
        calculateDxDy();
        // apply hooke's law to each attached mass
        Force f = new Force(Force.angleBetween(myDX, myDY), myK
                * (myLength - Force.distanceBetween(myDX, myDY)));
        myStart.applyForce(f);
        f.negate();
        myEnd.applyForce(f);
    }

    /**
     * Returns x-coordinate of the starting end of the spring.
     *
     * @return x coordinate of point
     */
    public final int getStartX () {
        return myStartX;
    }

    /**
     * Returns y-coordinate of the starting end of the spring.
     *
     * @return y coordinate of point
     */
    public final int getStartY () {
        return myStartY;
    }

    /**
     * Returns x-coordinate of the end of the spring.
     *
     * @return x coordinate of point
     */
    public final int getEndX () {
        return myEndX;
    }

    /**
     * Returns Y-coordinate of the end of the spring.
     *
     * @return y coordinate of point
     */
    public final int getEndY () {
        return myEndY;
    }

    /**
     * Returns the length of the spring at resting length.
     *
     * @return length of resting spring
     */
    public final double getRestLength () {
        return myLength;
    }

    /**
     * Sets the length of the spring at rest.
     *
     * @param newLength new spring rest length
     */
    public final void setRestLength (final double newLength) {
        myLength = newLength;
    }

    /**
     * Returns the spring constant.
     *
     * @return spring constant
     */
    public final double getK () {
        return myK;
    }

    /**
     * Returns reference to Mass located at the first end of the spring.
     *
     * @return starting mass
     */
    public final Mass getStartMass () {
        return myStart;
    }

    /**
     * Returns reference to mass located at the second end of the spring.
     *
     * @return ending mass
     */
    public final Mass getEndMass () {
        return myEnd;
    }

    /**
     * Returns the horizontal span of the spring.
     *
     * @return horizontal length
     */
    public final double getDx () {
        return myDX;
    }

    /**
     * Returns the vertical span of the spring.
     *
     * @return vertical length
     */
    public final double getDy () {
        return myDY;
    }
}
